package eventReservationServer;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import authorizationRMI.IAuthorizationRMI;
import authorizationRMI.InvalidPasswordException;
import authorizationRMI.InvalidUserException;
import authorizationRMI.User;

import RMIClient.UserRMIDTO;

import util.observer.rmi.IRemoteObserver;

import eventReservationServer.dao.EventServerDAO;
import eventReservationServer.dao.IEventServerDAO;
import eventReservationServer.eventServerGateway.EventDO;
import eventReservationServer.eventServerGateway.EventServerGatewayFactory;
import eventReservationServer.eventServerGateway.IEventServerGateway;
import eventReservationServer.loginGateway.AuthRMIGateway;
import eventReservationServer.loginGateway.AuthWSGateway;
import eventReservationServer.loginGateway.IAuthorizeGateway;
import eventReservationServer.loginGateway.UserDO;
import eventReservationServer.loginGateway.ValidationException;
import eventReservationServer.saxParser.Service;
import eventReservationServer.saxParser.XMLServices;

public class ReservationEventServer extends UnicastRemoteObject implements IReservationEventServer
{
	private static final long serialVersionUID = 1L;
	
	/**
	 * 
	 */
	
	/**
	 * Objeto para las tareas de suscripciones, eliminaciones y notificaciones.
	 */
	private util.observer.rmi.RemoteObservable remoteObservable = null;
	
	/**
	 * CACHE OF EventRMIDTO objects, organized by Event Name To be
	 * loaded at the initialization process
	 */
	private Map<String, Map<String, EventRMIDTO>> eventsCache;

	public ReservationEventServer() throws RemoteException
	{	
		// 1. Preparar el sistema de suscripción remoto
		remoteObservable = new util.observer.rmi.RemoteObservable();
	
		// 2. Preparar la caché de objetos de tipo EventRMIDTO
		// Creates the TreeMap
		eventsCache = new TreeMap<String, Map<String, EventRMIDTO>>();

		// Creo una conexión a la BD de reservas
		IEventServerDAO dao = new EventServerDAO();
		try
		{
			dao.connect();
		} catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		} catch (SQLException e)
		{
			e.printStackTrace();
		}
		
		// Get all services from the XML file
		Collection<Service> services = XMLServices.getServices();
		
		// For each service
		for (Service service : services)
		{						
			// Add the new server TreeMap
			eventsCache.put(service.getName(), new TreeMap<String, EventRMIDTO>());
			
			// Obtener el gateway del server y obtener sus eventos 
			List<EventDO> events = null;
			
			// Get the gateway from the factory
			IEventServerGateway gateway = null;
			try
			{
				gateway = EventServerGatewayFactory.getSingleton().getEventServerGateway(service.getName(), service.getIp(), service.getPort(), service.getTech().toString());
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
			
			// Get the event
			events = gateway.getEvents();
			
			// For each event
			for (EventDO eventDO : events)
			{
				// Consultar las plazas disponibles
				int availablePlaces = eventDO.getaAvailablePlaces() - dao.getPlacesCount(service.getName(), eventDO.getName());
				
				// Añadir el evento a la caché
				eventsCache.get(service.getName()).put(eventDO.getName(), new EventRMIDTO(service.getName(), eventDO.getName(), eventDO.getDate(), eventDO.getDescription(), availablePlaces, eventDO.getEventID()));
			}
		}
		
		// Desconexión de la BD de reservas
		try
		{
			dao.disconnect();
		} catch (SQLException e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		// Crea el servidor
		ReservationEventServer objServidor = null;
		try
		{
			objServidor = new ReservationEventServer();
		}
		catch (RemoteException e1)
		{
			e1.printStackTrace();
		}
		
		// 3. Registrarse en el RMIRegistry
		
		if (System.getSecurityManager() == null)
		{
			System.setSecurityManager(new RMISecurityManager());
		}
		String name = "//" + util.Settings.BESOCIAL_SERVER_IP + ":" + util.Settings.BESOCIAL_SERVER_PORT + "/" + "RMIServer";
		
		try
		{
			
			Naming.rebind(name, objServidor);
			System.out.println("Servidor '" + name + "' active and waiting...");
		}
		catch (Exception e)
		{
			System.err.println("Hello exception: " + e.getMessage());
			e.printStackTrace();
		}
		
		// Solo debug
		//System.out.println(objServidor.eventsCache);
	}

	@Override
	public void addRemoteObserver(IRemoteObserver arg0) throws RemoteException
	{
		remoteObservable.addRemoteObserver(arg0);
		
		System.out.println("Observador remoto añadido."); // registro
	}

	@Override
	public void deleteRemoteObserver(IRemoteObserver arg0)
			throws RemoteException
	{
		remoteObservable.deleteRemoteObserver(arg0);
		
		System.out.println("Observador remoto eliminado."); // registro
	}
	
	@Override
	public UserRMIDTO login(String uname, String pass) throws RemoteException, ValidationException
	{		
		// Consultar al servidor RMI o WS si es correcto
		IAuthorizeGateway gateway = null;
		if (util.Settings.AUTHORIZATION_TYPE.equals("rmi"))
			gateway = new AuthRMIGateway();
		else if (util.Settings.AUTHORIZATION_TYPE.equals("ws"))
			gateway = new AuthWSGateway();
		
		UserDO uDO = gateway.login(uname, pass);
		User u = new User(uDO.getUsername(), uDO.getPassword(), uDO.getName(), uDO.getAge(), uDO.getSex());
		
		 // Si es correcto, registrar
		 //TODO FEDE ¿Qué hay que hacer?
		 
		 // Indicar respuesta
		 System.out.println("Usuario validado correctamente."); // registro
		 return new UserRMIDTO(u.getUsername(), u.getPassword(), u.getName(), u.getSex(), u.getAge());
	}

	@Override
	public List<EventRMIDTO> getEvents() throws RemoteException
	{
		// Create the result type
		List<EventRMIDTO> list = new ArrayList<EventRMIDTO>();
		
		// Get the events cache
		Map<String, Map<String, EventRMIDTO>> mapmap = eventsCache;
		
		// Parse from the events cache to the result type
		for (Map.Entry<String, Map<String, EventRMIDTO>> entry : mapmap
				.entrySet())
		{
			Map<String, EventRMIDTO> map = entry.getValue();
			for (Map.Entry<String, EventRMIDTO> subentry : map.entrySet())
			{
				list.add(subentry.getValue());
			}
		}
		
		System.out.println("Se han devuelto " + list.size() + " eventos."); // registro

		return list;
	}

	@Override
	public void reservePlace(String username, String eventServer,
			String eventName) throws RemoteException
	{
		
		// Insertar nueva fila en la BD
		IEventServerDAO dao = new EventServerDAO();
		try
		{
			dao.connect();
		} catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		} catch (SQLException e)
		{
			e.printStackTrace();
		}
		dao.reservePlace(username, eventServer, eventName);
				
		try
		{
			dao.disconnect();
		} catch (SQLException e)
		{
			e.printStackTrace();
		}
		
		// Actualizo la caché
		eventsCache.get(eventServer).get(eventName).setAvailablePlaces(eventsCache.get(eventServer).get(eventName).getAvailablePlaces()-1);
		
		// Creo el nuevo objeto
		int newPlaces = eventsCache.get(eventServer).get(eventName).getAvailablePlaces();
		EventRMIDTO event = new EventRMIDTO(eventServer, eventName, null, null, newPlaces, 0);
		
		// Notifico
		remoteObservable.notifyRemoteObservers(event);
		
		System.out.println("Se ha registrado y notificado a los clientes una nueva reserva."); // registro
	}
}